package panel

import (
	"encoding/json"
	"errors"
	"gitee.com/watertreestar/octans-device-sdk/log"
	"gitee.com/watertreestar/octans-device-sdk/shadow"
	"io"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"
)

type RecogniseType string

var (
	FaceResult   RecogniseType = "face_result"
	IDCardResult RecogniseType = "idcard_read"
	ICCardResult RecogniseType = "wgin_read"
)

var (
	ResultBodyInvalidError = errors.New("result body invalid")
	TimeoutError           = errors.New("time out")
	DeviceNotOnlineError   = errors.New("device not online")
)

const (
	DefaultHeartMapping    = "/heartbeat"
	DefaultUpStreamMapping = "/result"
)

// RecogniseResult 结果推送
type RecogniseResult struct {
	ResultType RecogniseType          `json:"result_type"`
	SN         string                 `json:"sn"`
	Content    map[string]interface{} `json:"content"`
}

type outbound struct {
	uuid string // device sn
	time time.Time
	Data []byte
}

type pollingConnection struct {
	serialNum string // device serial number
	data      chan []byte
}

type Connector struct {
	connectable        map[string]*pollingConnection
	maxPollingDuration time.Duration
	outBoundData       chan *outbound
	mutex              *sync.Mutex
	port               int
	resultChan         chan RecogniseResult
	logger             log.Logger
	waitChan           *sync.Map
}

func NewConnector(port int, logger log.Logger) *Connector {
	connector := Connector{
		connectable:        make(map[string]*pollingConnection),
		maxPollingDuration: time.Second * 30, // use for heartbeat timeout, reserved
		outBoundData:       make(chan *outbound),
		mutex:              &sync.Mutex{},
		port:               port,
		resultChan:         make(chan RecogniseResult),
		logger:             logger,
		waitChan:           &sync.Map{},
	}
	return &connector
}

func (c *Connector) Initialize() error {
	c.registerHandler()
	c.start()
	c.logger.Info("face recognise panel server started at: %d ", c.port)
	return nil
}

func (c *Connector) Destroy() error {
	close(c.resultChan)
	close(c.outBoundData)
	return nil
}

// GetResultChan 获取识别结果通道
func (c *Connector) GetResultChan() chan RecogniseResult {
	return c.resultChan
}

// AddDevice 添加设备
func (c *Connector) AddDevice(sn, serialNum string) error {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	if err := shadow.Of().AddDevice(sn, time.Minute*3); err != nil {
		return err
	}
	c.connectable[sn] = &pollingConnection{
		serialNum: strings.ToUpper(serialNum),
		data:      make(chan []byte),
	}
	return nil
}

// RemoveDevice 删除设备
func (c *Connector) RemoveDevice(sn string) error {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	if err := shadow.Of().RemoveDevice(sn); err != nil {
		return err
	}

	connectable := c.connectable
	var filter = make(map[string]*pollingConnection)
	for k, c := range connectable {
		if k != sn {
			filter[k] = c
		}
	}
	c.connectable = filter
	return nil
}

// Send 向某个人脸识别面板机发送指令
func (c *Connector) Send(sn string, data map[string]interface{}, timeout time.Duration) (result LastResult, err error) {
	online, err := shadow.Of().GetStatus(sn)
	if err != nil {
		return LastResult{}, err
	}
	if !online {
		return LastResult{}, DeviceNotOnlineError
	}

	if len(sn) == 0 {
		return LastResult{}, errors.New("empty client id")
	}
	seq, writeable, err := decorate(data)
	if err != nil {
		return LastResult{}, err
	}
	c.outBoundData <- &outbound{sn, time.Now(), writeable}
	var wc = make(chan LastResult)
	c.waitChan.Store(seq, wc)
	if timeout < 0 {
		timeout = time.Second
	}

	select {
	case r := <-wc:
		c.waitChan.Delete(seq)
		return r, nil
	case <-time.After(timeout):
		c.waitChan.Delete(seq)
		return LastResult{}, TimeoutError
	}
}

func (c *Connector) registerHandler() {
	http.HandleFunc(DefaultHeartMapping, func(writer http.ResponseWriter, request *http.Request) {
		if request.Method != http.MethodPost {
			writer.WriteHeader(http.StatusMethodNotAllowed)
			return
		}
		if err := c.handleHeartbeat(writer, request); err != nil {
			writer.WriteHeader(http.StatusBadRequest)
			return
		}
	})

	http.HandleFunc(DefaultUpStreamMapping, func(writer http.ResponseWriter, request *http.Request) {
		data, _ := io.ReadAll(request.Body)

		var unRecognized = make(map[string]interface{})
		if err := json.Unmarshal(data, &unRecognized); err != nil {
			writer.WriteHeader(http.StatusBadRequest)
			return
		}
		sequence, result, err := attemptRecognise(unRecognized)
		if err == nil {
			serialNum := result.SN
			var found = false
			for sn, conn := range c.connectable {
				if conn.serialNum == serialNum {
					result.SN = sn
					found = true
				}
			}
			if found {
				c.resultChan <- result
				if _, err := writer.Write(newSuccessAckOutput(sequence)); err != nil {
					c.logger.Error("write to http response error", err)
				}
			}
		}
	})
}

func (c *Connector) start() {
	go c.run()

	addr := ":" + strconv.Itoa(c.port)
	go func() {
		_ = http.ListenAndServe(addr, nil)
	}()
}

func (c *Connector) run() {
	for {
		select {
		case e := <-c.outBoundData:
			{
				s, ok := c.connectable[e.uuid]
				if ok {
					s.data <- e.Data
				}
			}
		}
	}
}

func (c *Connector) handleHeartbeat(w http.ResponseWriter,
	r *http.Request) error {
	if err := r.ParseMultipartForm(0); err != nil {
		return err
	}
	var formVal = r.MultipartForm.Value
	var input = make(map[string]string)
	for key, val := range formVal {
		if len(val) == 1 {
			input[key] = val[0]
		}
	}
	b, _ := json.Marshal(input)
	var inputContent Input
	if err := json.Unmarshal(b, &inputContent); err != nil {
		return err
	}
	serialNum := inputContent.SerialNum
	if len(serialNum) == 0 {
		return errors.New("serial number not found")
	}

	connection, sn := c.inboundHeartbeat(serialNum)
	if connection == nil {
		w.WriteHeader(http.StatusUnauthorized)
		return nil
	}
	_ = shadow.Of().RefreshUpdateAt(sn)
	disconnectNotify := r.Context().Done()
	if inputContent.isAckReply() {
		seq := inputContent.LastResult.Index
		value, ok := c.waitChan.Load(seq)
		if ok {
			ch := value.(chan LastResult)
			ch <- inputContent.LastResult
		}
		w.Header().Add("Content-Type", "application/json")
		if _, err := w.Write(newSuccessAckOutput(inputContent.LastResult.Index)); err != nil {
			return err
		}
	} else {
		select {
		case d := <-connection.data:
			_, err := w.Write(d)
			if err != nil {
				c.logger.Error("write http heartbeat error", err)
			}
		case <-disconnectNotify:
			return nil
		}
	}
	return nil
}

func (c *Connector) inboundHeartbeat(serialNum string) (connection *pollingConnection, sn string) {
	for sn, polling := range c.connectable {
		if polling.serialNum == serialNum {
			return polling, sn
		}
	}
	return nil, ""
}
